<?php
class Date
{
  /**
   * The timestamp for this Date instance.
   */
  private $ts = null;

  /**
   * The original timestamp for this Date instance.
   */
  private $init = null;

  /**
   * Retrieves a new instance of this class.
   *
   * NOTE: This is not the singleton pattern. Instead, it is for chainability ease-of-use.
   *
   * <b>Example:</b>
   * <code>
   *   echo Date::getInstance()->getFirstDayOfWeek()->addDay()->format('%Y-%m-%d');
   * </code>
   *
   * @param mixed timestamp, string, or Date object
   * @return  Date
   */
  public static function getInstance($value = null)
  {
    return new Date($value);
  }

  /**
   * Construct an Date object.
   *
   * @param mixed timestamp, string, or Date object
   */
  public function __construct($value = null)
  {
    $this->set($value);
  }

  /**
   * Clone this object
   *
   * @return Date a copy of this object
  */
  public function copy()
  {
    // TODO should that be a clone $this ?
    // - what about the $this->init ?
    // - jme- this is a modification
    return new Date($this);
  }

  /**
   * Format the date according to the <code>date</code> function.
   *
   * @return  string
   */
  public function format($format)
  {
    return date($format, $this->ts);
  }
  
  public function __toString()
  {
    return $this->dump();
  }
  /**
   * Format the date as a datetime value.
   *
   * @return  string
   */
  public function dump()
  {
    // jme- do something to get it configurable via .yml somewhere
    return date('Y-m-d H:i:s', $this->ts);
  }

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//    all the Date::format_*() function
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

  /**
   * return a string representation of the Date for human (only hours)
   *
   * - the format may be configured in sfConfig app_DateTimePlugin_format_hour_human
   * - if not present, it default to 'H:i:s'
   *
   * - jme- this is a modification
   *
   * @return  string
   */
  public function format_hour($format = "H:i:s")
  {
    return date($format, $this->ts);
  }

  /**
   * return a string representation of the Date for human
   *
   * - the format may be configured in sfConfig app_DateTimePlugin_format_human
   * - if not present, it default to '%Y-%m-%d'
   *
   * - jme- this is a modification
   *
   * @return  string
   */
  public function format_human($format = "%Y-%m-%d")
  {
    // build the string and return it
    return $this->format_internal($format);
  }

  /**
   * return a string representation of the Date for database
   *
   * - the format may be configured in sfConfig app_DateTimePlugin_format_database
   * - if not present, it default to '%Y-%m-%d'
   *
   * - jme- this is a modification
   *
   * @return  string
   */
  public function format_database($format = "%Y-%m-%d")
  {
    // build the string and return it
    return $this->format_internal($format);
  }

  /**
   * return a string representation of the Date for system
   *
   * - the format may be configured in sfConfig app_DateTimePlugin_format_database
   * - if not present, it default to '%Y-%m-%d'
   *
   * @return  string
   */
  public function format_system($format = "%Y-%m-%d")
  {
    // build the string and return it
    return $this->format_internal($format);
  }

  /**
   * Internal formating of this object to a string according to $format_str
  */
  private function format_internal($format_str)
  {
    // build the string and return it
    return strftime($format_str, $this->ts);
  }

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//    bunch of alias
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

  /**
   * Alias of ->format_system()
   */
  public function to_system() { return $this->format_system();  }
  /**
   * Alias of ->format_database()
   */
  public function to_database() { return $this->format_database();  }
  /**
   * Alias of ->format_human()
   */
  public function to_human()  { return $this->format_human();   }

  public function o2s()   { return $this->to_system();    }
  public function o2d()   { return $this->to_database();    }
  public function o2h()   { return $this->to_human();   }


  public static function s2o($from) { return Date::from_system($from);    }
  public static function d2o($from) { return Date::from_database($from);  }
  public static function h2o($from) { return Date::from_human($from);   }

  public static function s2h($from) { return empty($from)? $from : Date::from_system($from)->to_human();  }
  public static function s2d($from) { return empty($from)? $from : Date::from_system($from)->to_database(); }
  public static function d2s($from) { return empty($from)? $from : Date::from_database($from)->to_system(); }
  public static function d2h($from) { return empty($from)? $from : Date::from_database($from)->to_human();  }
  public static function h2s($from) { return empty($from)? $from : Date::from_human($from)->to_system();  }
  public static function h2d($from) { return empty($from)? $from : Date::from_human($from)->to_database();  }


/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//    all the Date::from_*() function
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

  /**
   * return a Date from a string formated for "system"
   *
   * - the format may be configured in sfConfig app_DateTimePlugin_format_system
   * - if not present, it default to '%Y-%m-%d'
   *
   * @return  Date
   */
  private static function from_internal($date_str, $format)
  {
    // log to debug
    //ezDbg::err("myformat=".$format." and date_str=".$date_str);
    // parse $date_str according to $format
    $arr  = strptime($date_str, $format);
    // convert it to a UNIX timestamp
    $ts = mktime($arr['tm_hour'], $arr['tm_min'], $arr['tm_sec'], $arr['tm_mon']+1
            , $arr['tm_mday'], $arr['tm_year'] + 1900);
    // return a Date
    return new Date($ts);
  }

  /**
   * return a Date from a string formated for "system"
   *
   * - the format may be configured in sfConfig app_DateTimePlugin_format_system
   * - if not present, it default to '%Y-%m-%d'
   *
   * @return  Date
   */
  public static function from_system($date_str, $format = "%Y-%m-%d")
  {
    // return a Date
    return self::from_internal($date_str, $format);
  }

  /**
   * return a Date from a string formated for "database"
   *
   * - the format may be configured in sfConfig app_DateTimePlugin_format_database
   * - if not present, it default to '%Y-%m-%d'
   *
   * @return  Date
   */
  public static function from_database($date_str, $format = "%Y-%m-%d")
  {
    // return a Date
    return self::from_internal($date_str, $format);
  }

  /**
   * return a Date from a string formated for "human"
   *
   * - the format may be configured in sfConfig app_DateTimePlugin_format_human
   * - if not present, it default to '%Y-%m-%d'
   *
   * @return  Date
   */
  public static function from_human($date_str, $format = "%Y-%m-%d")
  {
    // return a Date
    return self::from_internal($date_str, $format);
  }

  /**
   * Retrieves the given unit of time from the timestamp.
   *
   * @param int unit of time (accepts Time constants).
   * @return  int the unit of time
   *
   * @throws  DateTimeException
   */
  public function retrieve($unit = Time::DAY)
  {
    switch ($unit)
    {
      case Time::SECOND:
        return date('s', $this->ts);
      case Time::MINUTE:
        return date('i', $this->ts);
      case Time::HOUR:
        return date('H', $this->ts);
      case Time::DAY:
        return date('d', $this->ts);
      case Time::WEEK:
        return date('W', $this->ts);
      case Time::MONTH:
        return date('m', $this->ts);
      case Time::QUARTER:
        return ceil(date('m', $this->ts) / 3);
      case Time::YEAR:
        return date('Y', $this->ts);
      case Time::DECADE:
        return ceil((date('Y', $this->ts) % 100) / 10);
      case Time::CENTURY:
        return ceil(date('Y', $this->ts) / 100);
      case Time::MILLENIUM:
        return ceil(date('Y', $this->ts) / 1000);
      default:
        throw new DateTimeException(sprintf('The unit of time provided is not valid: %s', $unit));
    }
  }

  /**
   * Retrieve the timestamp value of this Date instance.
   *
   * @return  timestamp
   */
  public function get()
  {
    return $this->ts;
  }

  /**
   * Sets the timestamp value of this Date instance.
   *
   * This function accepts several froms of a date value:
   * - timestamp
   * - string, parsed with <code>strtotime</code>
   * - Date object
   *
   * @return  Date  the modified object, for chainability
   */
  public function set($value = null)
  {
    // get the timestamp from $value
    $ts   = DateTimeToolkit::getTS($value);

    // copy the timestamp to the
    $this->ts = $ts;
    if ($this->init === null) $this->init = $ts;

    // return the object itself
    return $this;
  }

  /**
   * Resets the timestamp value of this Date instance to its original value.
   *
   * @return  Date  the reset object, for chainability
   */
  public function reset()
  {
    $this->ts = $this->init;

    return $this;
  }

  /**
   * Compares two date values.
   *
   * @param mixed timestamp, string, or Date object
   * @return  int   -1, 0, or 1
   */
  public function cmp($value)
  {
    $ts = DateTimeToolkit::getTS($value);

    if ($this->ts < $ts)
    {
      // less than
      return -1;
    }
    else if ($this->ts > $ts)
    {
      // greater than
      return 1;
    }

    // equal to
    return 0;
  }

  /**
   * Gets the difference of two date values in a Time unit
   *
   * @param mixed timestamp, string, or Date object
   * @param int the unit to diff by (default to Time::SECOND)
   * @return  int the difference in the unit
   * @throws  DateTimeException
   */
  public function diff($other, $unit = Time::SECOND)
  {
    // jme- modification to get a $unit
    $other_ts = DateTimeToolkit::getTS($other);
    $diff_ts  = $this->ts - $other_ts;

    // determine which unit of time to add by
    switch ($unit)
    {
      case Time::SECOND:
        $factor = 1;
        break;
      case Time::MINUTE:
        $factor = 1*60;
        break;
      case Time::HOUR:
        $factor = 1*60*60;
        break;
      case Time::DAY:
        $factor = 1*60*60*24;
        break;
      case Time::WEEK:
        $factor = 1*60*60*24*7;
        break;
      // jme- not doing the rest due to some special cases
      // - TODO to do later
      // - e.g. how many days in a MONTH ? 28, 29, 30, 31 ?
      //   - rought approximation ok ? NO! as it may lead to confusion
      case Time::MONTH:
      case Time::QUARTER:
      case Time::YEAR:
      case Time::DECADE:
      case Time::CENTURY:
      case Time::MILLENIUM:
      default:
        throw new DateTimeException(sprintf('The unit of time provided is not valid: %s', $unit));
    }

    // compute and return the result
    return (int)(($diff_ts + ($factor/2)) / $factor);
  }

  /**
   * Gets the difference of two date values in Time::SECOND
   *
   * @param mixed timestamp, string, or Date object
   * @return  int the difference in the unit
   * @throws  DateTimeException
   */
  public function diffSecond($other)
  {
    return $this->diff($other, Time::SECOND);
  }
  /**
   * Gets the difference of two date values in Time::MINUTE
   *
   * @param mixed timestamp, string, or Date object
   * @return  int the difference in the unit
   * @throws  DateTimeException
   */
  public function diffMinute($other)
  {
    return $this->diff($other, Time::MINUTE);
  }
  /**
   * Gets the difference of two date values in Time::HOUR
   *
   * @param mixed timestamp, string, or Date object
   * @return  int the difference in the unit
   * @throws  DateTimeException
   */
  public function diffHour($other)
  {
    return $this->diff($other, Time::HOUR);
  }
  /**
   * Gets the difference of two date values in Time::DAY
   *
   * @param $other  mixed timestamp, string, or Date object
   * @return  int the difference in the unit
   * @throws  DateTimeException
   */
  public function diffDay($other)
  {
    return $this->diff($other, Time::DAY);
  }
  /**
   * Gets the difference of two date values in Time::WEEK
   *
   * @param $other  mixed timestamp, string, or Date object
   * @return  int   the difference in the unit
   * @throws  DateTimeException
   */
  public function diffWeek($other)
  {
    return $this->diff($other, Time::WEEK);
  }

  /********************************************************************************/
  /********************************************************************************/
  /*    isHollyday() functions            */
  /********************************************************************************/
  /********************************************************************************/
  /**
   * @return  boolean   true if the current day is Holiday
  */
  public function isHoliday($culture)
  {
    if( $culture == "fr" )  return isHolidayFr();
    throw new DateTimeException("the culture ".$culture." is not implemented. sorry");
  }

  /**
   * @return  boolean   true if date is Holiday in culture 'fr'
  */
  public function isHolidayFr()
  {
    // add fixed holydays
    $holidays = array(
      "Nouvel an"   => array( 'month' => 1  , 'day' => 1  ),
      "Armistice 39-45" => array( 'month' => 5  , 'day' => 8  ),
      "Toussaint"   => array( 'month' => 11 , 'day' => 1  ),
      "Armistice 14-18" => array( 'month' => 11 , 'day' => 11 ),
      "Assomption"    => array( 'month' => 8  , 'day' => 15 ),
      "Fete du travail" => array( 'month' => 5  , 'day' => 1  ),
      "Fete nationnale" => array( 'month' => 7  , 'day' => 14 ),
      "Noel"      => array( 'month' => 12 , 'day' => 25 ),
    );

    // add mobile holidays too

    // add easter_day
    $easter_monday_date = Date::getInstance(easter_date($this->getYear()))->addDay(1);
    $holidays['Lundi de Paques']  = array( 'month'  => $easter_monday_date->getMonth(),
               'day'    => $easter_monday_date->getDay()
              );
    $ascension_date = Date::getInstance(easter_date($this->getYear()))->addDay(39);
    $holidays['Ascenscion']   = array( 'month'  => $ascension_date->getMonth(),
               'day'    => $ascension_date->getDay()
              );
    $pentecote_date = Date::getInstance(easter_date($this->getYear()))->addDay(49);
    $holidays['Pentecote']    = array( 'month'  => $pentecote_date->getMonth(),
               'day'    => $pentecote_date->getDay()
              );

    // test if the current date is in Holidays
    $cur_month  = $this->retrieve(Time::MONTH);
    $cur_day  = $this->retrieve(Time::DAY);
    foreach($holidays as $key => $val){
      // if not the proper month, goto the next
      if( $cur_month  != $val['month'] )  continue;
      // if not the proper day, goto the next
      if( $cur_day  != $val['day'] )  continue;
      // return true;
      return true;
    }
    // if all the tests passed
    return false;
  }

  /********************************************************************************/
  /********************************************************************************/
  /*    all the getUNIT() functions         */
  /********************************************************************************/
  /********************************************************************************/

  /**
   * - only a shortcut on Date::retrieve
   * @return integer return the seconds
  */
  public function getSecond(){  return $this->retrieve(Time::SECOND);   }
  /**
   * - only a shortcut on Date::retrieve
   * @return integer return the minutes
  */
  public function getMinute(){  return $this->retrieve(Time::MINUTE);   }
  /**
   * - only a shortcut on Date::retrieve
   * @return integer return the minutes
  */
  public function getHour(){  return $this->retrieve(Time::HOUR);   }
  /**
   * - only a shortcut on Date::retrieve
   * @return integer return the days
  */
  public function getDay(){ return $this->retrieve(Time::DAY);    }
  /**
   * - only a shortcut on Date::retrieve
   * @return integer return the month
  */
  public function getMonth(){ return $this->retrieve(Time::MONTH);    }
  /**
   * - only a shortcut on Date::retrieve
   * @return integer return the year
  */
  public function getYear(){  return $this->retrieve(Time::YEAR);   }
  /**
   * - only a shortcut on Date::retrieve
   * @return integer return the decade
  */
  public function getDecade(){  return $this->retrieve(Time::YEAR);   }
  /**
   * - only a shortcut on Date::retrieve
   * @return integer return the century
  */
  public function getCentury(){ return $this->retrieve(Time::CENTURY);  }
  /**
   * - only a shortcut on Date::retrieve
   * @return integer return the millenium
  */
  public function getMillenium(){ return $this->retrieve(Time::MILLENIUM);  }

  /**
   * Returns the timestamp for first day of the week for the given date.
   *
   * @param timestamp
   * @return  timestamp
   */
  public function dayOfWeek()
  {
    return date('w', $this->ts);
  }

  /********************************************************************************/
  /********************************************************************************/
  /*    Utility functions     */
  /********************************************************************************/
  /********************************************************************************/


  /**
   * check if the year is bisextil or not
   *
   * @return boolean
   */

  public function isBisextil()
  {

    return (bool) (1 == date('L',$this->ts) );

  }

  /**
   * Call any function available in the Time library, but without the ts parameter.
   *
   * <b>Example:</b>
   * <code>
   *   $ts = Time::firstDayOfMonth(Time::addMonth(time(), 5));
   *   // equivalent
   *   $dt = new Date();
   *   $ts = $dt->addMonth(5)->firstDayOfMonth()->get();
   * </code>
   *
   * @return  Date  the modified object, for chainability
   */
  public function __call($method, $arguments)
  {
    $callable = array('Time', $method);

    if (!is_callable($callable))
    {
      throw new DateTimeException(sprintf('Call to undefined function: %s::%s', 'Date', $method));
    }

    array_unshift($arguments, $this->ts);

    $this->ts = call_user_func_array($callable, $arguments);

    return $this;
  }
}

//------------------------------------------------------------------------------

class Time
{
  /**
   * Units of time
   */
  const SECOND  = 0;
  const MINUTE  = 1;
  const HOUR  = 2;
  const DAY = 3;
  const WEEK  = 4;
  const MONTH = 5;
  const QUARTER = 6;
  const YEAR  = 7;
  const DECADE  = 8;
  const CENTURY = 9;
  const MILLENIUM = 10;

  /**
   * Days of the week
   * - TODO jme this should not be sunday the first day all the time
   *   - for example in many european country monday is the first day
   */
  const SUNDAY  = 0;
  const MONDAY  = 1;
  const TUESDAY = 2;
  const WEDNESDAY = 3;
  const THURSDAY  = 4;
  const FRIDAY  = 5;
  const SATURDAY  = 6;

  /**
   * Months of the year
   */
  const JANUARY = 1;
  const FEBRUARY  = 2;
  const MARCH = 3;
  const APRIL = 4;
  const MAY = 5;
  const JUNE  = 6;
  const JULY  = 7;
  const AUGUST  = 8;
  const SEPTEMBER = 9;
  const OCTOBER = 10;
  const NOVEMBER  = 11;
  const DECEMBER  = 12;

  /**
   * Adds the specified number of given units of time to the given date.
   *
   * <b>Example:</b>
   * <code>
   *   // tomorrow
   *   $dt = Time::add();
   *   // day after
   *   $dt = Time::add($mydate);
   *   // 5 days after
   *   $dt = Time::add($mydate, 5);
   *   // 2 months after
   *   $dt = Time::add($mydate, 2, Time::MONTH);
   *   // 4 weeks after
   *   $dt = Time::add($mydate, 4, Time::WEEK);
   * </code>
   *
   * @param timestamp a timestamp for the calculation
   * @param int     the number of units to add to the given date
   * @param int     the unit to add by
   * @return  timestamp the timestamp result of the calculation
   *
   * @throws  DateTimeException
   */
  public static function add($ts = null, $num = 1, $unit = Time::DAY)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    // gather individual variables for readability and maintainability
    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    // determine which unit of time to add by
    switch ($unit)
    {
      case Time::SECOND:
        return mktime($H, $i, $s + $num, $m, $d, $Y);
      case Time::MINUTE:
        return mktime($H, $i + $num, $s, $m, $d, $Y);
      case Time::HOUR:
        return mktime($H + $num, $i, $s, $m, $d, $Y);
      case Time::DAY:
        return mktime($H, $i, $s, $m, $d + $num, $Y);
      case Time::WEEK:
        return mktime($H, $i, $s, $m, $d + (7 * $num), $Y);
      case Time::MONTH:
        return mktime($H, $i, $s, $m + $num, $d, $Y);
      case Time::QUARTER:
        return mktime($H, $i, $s, $m + (3 * $num), $d, $Y);
      case Time::YEAR:
        return mktime($H, $i, $s, $m, $d, $Y + $num);
      case Time::DECADE:
        return mktime($H, $i, $s, $m, $d, $Y + (10 * $num));
      case Time::CENTURY:
        return mktime($H, $i, $s, $m, $d, $Y + (100 * $num));
      case Time::MILLENIUM:
        return mktime($H, $i, $s, $m, $d, $Y + (1000 * $num));
      default:
        throw new DateTimeException(sprintf('The unit of time provided is not valid: %s', $unit));
    }
  }

  /**
   * Subtracts the specified number of given units of time from the given date.
   *
   * <b>Example:</b>
   * <code>
   *   // yesterday
   *   $dt = Time::subtract();
   *   // day before
   *   $dt = Time::subtract($mydate);
   *   // 5 days before
   *   $dt = Time::subtract($mydate, 5);
   *   // 2 months before
   *   $dt = Time::subtract($mydate, 2, Time::MONTH);
   *   // 4 weeks before
   *   $dt = Time::subtract($mydate, 4, Time::WEEK);
   * </code>
   *
   * @param timestamp a timestamp for the calculation
   * @param int   the number of units to add to the given date
   * @param int   the unit to add by
   * @return  timestamp the timestamp result of the calculation
   *
   * @see   add
   */
  public static function subtract($ts = null, $num = 1, $unit = Time::DAY)
  {
    return Time::add($ts, $num * -1, $unit);
  }

  /**
   * Returns the timestamp with the date but without the time of day.
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function clearTime($ts = null)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime(0, 0, 0, $m, $d, $Y);
  }
  
  /**
   * Returns the timestamp with the date but the time of day at 23:59:59
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function lastTime($ts = null)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime(23, 59, 59, $m, $d, $Y);
  }

  /**
   * Returns the timestamp with the time of day but without the date.
   *
   * @deprecated This is a deprecated function. Do not use!
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function clearDate($ts = null)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime($H, $i, $s, 0, 0, 0);
  }

  /**
   * Clear the second value of this timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function clearSecond($ts = null)
  {
    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime($H, $i, 0, $m, $d, $Y);
  }

  /**
   * Clear the minute value of this timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function clearMinute($ts = null)
  {
    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime($H, 0, $s, $m, $d, $Y);
  }

  /**
   * Clear the hour value of this timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function clearHour($ts = null)
  {
    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime(0, $i, $s, $m, $d, $Y);
  }

  /**
   * Set the second value of this timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function setSecond($ts = null, $second = 0)
  {
    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime($H, $i, $second, $m, $d, $Y);
  }

  /**
   * Set the minute value of this timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function setMinute($ts = null, $minute = 0)
  {
    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime($H, $minute, $s, $m, $d, $Y);
  }

  /**
   * Set the hour value of this timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function setHour($ts = null, $hour = 0)
  {
    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime($hour, $i, $s, $m, $d, $Y);
  }

  /**
   * Set the day value of this timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function setDay($ts = null, $day = 1)
  {
    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime($H, $i, $s, $m, $day, $Y);
  }

  /**
   * Set the month value of this timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function setMonth($ts = null, $month = 1)
  {
    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime($H, $i, $s, $month, $d, $Y);
  }

  /**
   * Set the year value of this timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function setYear($ts = null, $year = 1970)
  {
    list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);

    return mktime($H, $i, $s, $m, $d, $year);
  }

  /**
   * Returns the timestamp for tomorrow.
   *
   * Alias for Time::addDay
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function tomorrow($ts = null)
  {
    return Time::add($ts);
  }

  /**
   * Returns the timestamp for yesterday.
   *
   * Alias for Time::subtractDay
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function yesterday($ts = null)
  {
    return Time::subtract($ts);
  }

  /**
   * Adds the specified number of seconds to the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function addSecond($ts = null, $num = 1)
  {
    return Time::add($ts, $num, Time::SECOND);
  }

  /**
   * Subtracts the specified number of seconds from the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function subtractSecond($ts = null, $num = 1)
  {
    return Time::subtract($ts, $num, Time::SECOND);
  }

  /**
   * Adds the specified number of minutes to the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function addMinute($ts = null, $num = 1)
  {
    return Time::add($ts, $num, Time::MINUTE);
  }

  /**
   * Subtracts the specified number of minutes from the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function subtractMinute($ts = null, $num = 1)
  {
    return Time::subtract($ts, $num, Time::MINUTE);
  }

  /**
   * Adds the specified number of hours to the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function addHour($ts = null, $num = 1)
  {
    return Time::add($ts, $num, Time::HOUR);
  }

  /**
   * Subtracts the specified number of hours from the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function subtractHour($ts = null, $num = 1)
  {
    return Time::subtract($ts, $num, Time::HOUR);
  }

  /**
   * Adds the specified number of days to the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function addDay($ts = null, $num = 1)
  {
    return Time::add($ts, $num, Time::DAY);
  }

  /**
   * Subtracts the specified number of days from the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function subtractDay($ts = null, $num = 1)
  {
    return Time::subtract($ts, $num, Time::DAY);
  }

  /**
   * Adds the specified number of weeks to the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function addWeek($ts = null, $num = 1)
  {
    return Time::add($ts, $num, Time::WEEK);
  }

  /**
   * Subtracts the specified number of weeks from the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function subtractWeek($ts = null, $num = 1)
  {
    return Time::subtract($ts, $num, Time::WEEK);
  }

  /**
   * Adds the specified number of months to the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function addMonth($ts = null, $num = 1)
  {
    return Time::add($ts, $num, Time::MONTH);
  }

  /**
   * Subtracts the specified number of months from the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function subtractMonth($ts = null, $num = 1)
  {
    return Time::subtract($ts, $num, Time::MONTH);
  }

  /**
   * Adds the specified number of quarters to the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function addQuarter($ts = null, $num = 1)
  {
    return Time::add($ts, $num, Time::QUARTER);
  }

  /**
   * Subtracts the specified number of quarters from the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function subtractQuarter($ts = null, $num = 1)
  {
    return Time::subtract($ts, $num, Time::QUARTER);
  }

  /**
   * Adds the specified number of years to the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function addYear($ts = null, $num = 1)
  {
    return Time::add($ts, $num, Time::YEAR);
  }

  /**
   * Subtracts the specified number of years from the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function subtractYear($ts = null, $num = 1)
  {
    return Time::subtract($ts, $num, Time::YEAR);
  }

  /**
   * Adds the specified number of decades to the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function addDecade($ts = null, $num = 1)
  {
    return Time::add($ts, $num, Time::DECADE);
  }

  /**
   * Subtracts the specified number of decades from the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function subtractDecade($ts = null, $num = 1)
  {
    return Time::subtract($ts, $num, Time::DECADE);
  }

  /**
   * Adds the specified number of centuries to the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function addCentury($ts = null, $num = 1)
  {
    return Time::add($ts, $num, Time::CENTURY);
  }

  /**
   * Subtracts the specified number of centuries from the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function subtractCentury($ts = null, $num = 1)
  {
    return Time::subtract($ts, $num, Time::CENTURY);
  }

  /**
   * Adds the specified number of millenia to the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function addMillenium($ts = null, $num = 1)
  {
    return Time::add($ts, $num, Time::MILLENIUM);
  }

  /**
   * Subtracts the specified number of millenia from the timestamp.
   *
   * @param timestamp
   * @param int
   * @return  timestamp
   */
  public static function subtractMillenium($ts = null, $num = 1)
  {
    return Time::subtract($ts, $num, Time::MILLENIUM);
  }

  /**
   * Returns the timestamp for first day of the week for the given date.
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function firstDayOfWeek($ts = null)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    return Time::subtractDay($ts, date('w', $ts));
  }

  /**
   * Returns the timestamp for last day of the week for the given date.
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function finalDayOfWeek($ts = null)
  {
    return Time::subtractDay(Time::firstDayOfWeek(Time::addWeek($ts)));
  }

  /**
   * Returns the timestamp for first day of the month for the given date.
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function firstDayOfMonth($ts = null)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    return Time::subtractDay($ts, date('d', $ts) - 1);
  }

  /**
   * Returns the timestamp for last day of the month for the given date.
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function finalDayOfMonth($ts = null)
  {
   list($H, $i, $s, $m, $d, $Y) = DateTimeToolkit::breakdown($ts);
   return  mktime($H, $i, $s, $m, date('t',$ts), $Y);
  }

  /**
   * Returns the timestamp for first day of thequarter for the given date.
   *
   * NOTE: Computes the quarter as:
   * <code>
   *   $quarter = ceil(date('m', $ts) / 3); // 1 - 4
   * </code>
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function firstDayOfQuarter($ts = null)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    // variables for computation
    $month = date('m', $ts);
    $quarter = ceil($month / 3) - 1; // zero based quarter

    return Time::subtractMonth(Time::firstDayOfMonth($ts), $month - ($quarter * 3) - 1);
  }

  /**
   * Returns the timestamp for last day of the quarter for the given date.
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function finalDayOfQuarter($ts = null)
  {
    return Time::subtractDay(Time::firstDayOfQuarter(Time::addQuarter($ts)));
  }

  /**
   * Returns the timestamp for first day of the year for the given date.
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function firstDayOfYear($ts = null)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    return Time::subtractMonth(Time::firstDayOfMonth($ts), date('m', $ts) - 1);
  }

  /**
   * Returns the timestamp for last day of the year for the given date.
   *
   * @param timestamp
   * @return  timestamp
   */
  public static function finalDayOfYear($ts = null)
  {
    return Time::subtractDay(Time::firstDayOfYear(Time::addYear($ts)));
  }

  /**
   * Returns the timestamp for the next occurance of [day].
   *
   * @param timestamp
   * @param int     the day of week
   * @return  timestamp
   */
  public static function nextDay($ts = null, $day = Time::SUNDAY)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    // get offsets from sunday
    $offset1 = date('w', $ts);
    $offset2 = $day;

    // adjust if date wraps into next week
    $offset2 += $offset2 > $offset1 ? 0 : 7;

    return Time::addDay($ts, $offset2 - $offset1);
  }

  /**
   * Returns the timestamp for the most recent (previous) occurance of [day].
   *
   * @param timestamp
   * @param int     the day of week
   * @return  timestamp
   */
  public static function previousDay($ts = null, $day = Time::SUNDAY)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    // get offsets from sunday
    $offset1 = date('w', $ts);
    $offset2 = $day;

    // adjust if date wraps into last week
    $offset1 += $offset1 > $offset2 ? 0 : 7;

    return Time::subtractDay($ts, $offset1 - $offset2);
  }

  /**
   * Returns the timestamp for the next occurance of [month].
   *
   * @param timestamp
   * @param int     the month of year
   * @return  timestamp
   */
  public static function nextMonth($ts = null, $month = Time::JANUARY)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    // get offsets from january
    $offset1 = date('m', $ts);
    $offset2 = $month;

    // adjust if date wraps into next year
    $offset2 += $offset2 > $offset1 ? 0 : 12;

    return Time::addMonth($ts, $offset2 - $offset1);
  }

  /**
   * Returns the timestamp for the most recent (previous) occurance of [month].
   *
   * @param timestamp
   * @param int     the month of year
   * @return  timestamp
   */
  public static function previousMonth($ts = null, $month = Time::JANUARY)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();

    // get offsets from january
    $offset1 = date('m', $ts);
    $offset2 = $month;

    // adjust if date wraps into last year
    $offset1 += $offset1 > $offset2 ? 0 : 12;

    return Time::subtractMonth($ts, $offset1 - $offset2);
  }



}

//------------------------------------------------------------------------------

class DateTimeToolkit
{
  /**
   * Breaks down the individual components of the timestamp.
   *
   * @param timestamp
   * @return  array
   */
  public static function breakdown($ts = null)
  {
    // default to now
    if ($ts === null) $ts = DateTimeToolkit::now();
    
    // gather individual variables
    $H = date('H', $ts); // hour
    $i = date('i', $ts); // minute
    $s = date('s', $ts); // second
    $m = date('m', $ts); // month
    $d = date('d', $ts); // day
    $Y = date('Y', $ts); // year
    
    return array($H, $i, $s, $m, $d, $Y);
  }
  
  /**
   * Returns the current timestamp.
   *
   * @return  timestamp
   *
   * @see   time
   */
  public static function now()
  {
    return time();
  }
  
  /**
   * Retrieve the timestamp from a number of different formats.
   *
   * @param mixed value to use for timestamp retrieval
   */
  public static function getTS($value = null)
  {
    if ($value === null)
    {
      return DateTimeToolkit::now();
    }
    else if ($value instanceof Date)
    {
      return $value->get();
    }
    else if (!is_numeric($value))
    {
      return strtotime($value);
    }
    else if (is_numeric($value))
    {
      return $value;
    }
    
    throw new DateTimeException(sprintf('A timestamp could not be retrieved from the value: %s', $value));
  }
}
//------------------------------------------------------------------------------
class DateTimeException extends Exception
{
}
//------------------------------------------------------------------------------
function cms_get_datetimezones()
{
  $res = array();
  foreach (DateTimeZone::listIdentifiers() as $zone)
    $res[$zone] = $zone;
  return $res;
}
//------------------------------------------------------------------------------